אירועים ומטפלי אירועים, הוספת מטפלי אירועים, מחיקת מטפלי אירועים ודברים חשובים שצריכים לשים לב אליהם כשרושמים מטפל לאירוע.
מהו אירוע?
אז לחדשים מבנינו, אירוע הוא בעצם משהו שמתרחש. האירוע לרוב מתרחש על ידי המשתמש, אך לא תמיד (כמו שנראה בהמשך).
אירועים לדוגמה: לחיצה על העכבר, תזוזה של העכבר, לחיצה על מקש במקלדת וכו'. כל אלה הם אירועים.
מה עושים עם האירועים האלה?
לפעמים נרצה לעשות משהו מסוים דווקא כשמשהו קורה.
לדוגמה, נרצה להקפיץ הודעה כאשר המשתמש ילחץ על כפתור מסוים באתר שלנו.
בדיוק כאן נכנסים האירועים. כדי ליישם את הדוגמה שלנו אנחנו נצטרך להשתמש באירוע click. אנחנו נצטרך לתפוס את האירוע ולדאוג שקוד מסוים (הקפצת הודעה במקרה שלנו) יפעל כאשר האירוע מתרחש.
נשמע מורכב? ממש לא. לפחות לא בחלק הזה. :)
אירועים ומטפלי אירועים
כדי להתחיל, אנחנו נצטרך להבדיל בין האירועים לבין מטפלי האירועים.
האירועים - הדברים שמתרחשים כמו לחיצה על העכבר, לחיצה על כפתור במקלדת וכדומה.
מטפלי האירועים - מה שבעצם מתרחש כשהאירוע מופעל, הדבר שמטפל באירוע.
חשוב להבין שלפעמים לאירוע אין מטפלי אירוע, ובמקרה כזה פשוט לא יקרה כלום שהאירוע יתרחש.
לפעמים גם המצב ההפוך קיים. לפעמים יש מטפל לאירוע abc כשהאירוע abc בכלל לא קיים. דבר כזה קורה בדרך כלל כטעות של המתכנת ואין סיבה לכתוב מטפלים לאירועים שלא קיימים.
מטפל אירוע נקרא גם מאזין לאירוע (event listener) כי הוא בעצם "מקשיב" ומחכה לפעול ברגע שהאירוע יתרחש.
טיפול באירועים
כדי לטפל באירוע אנחנו נשתמש בפונקציה addEventListener.
הפונקצייה addEventListener מקבלת שלושה פרמטרים (הרביעי אינו חלק מהסטנדרט ולכן לא אתייחס אליו):
type - סוג האירוע. הכוונה היא ל-click במקרה של לחיצה, mouseover במקרה של מעבר עכבר וכדומה.
listener - מטפל האירוע. כאן נעביר פונקציה שתתבצע כאשר האירוע יתרחש. הערך של this בפונקציה הוא האלמנט אליו הצמדנו את מטפל האירוע.
useCapture (לא חובה) - קשה יותר להבין את המשמעות שלו מאשר שאר הפרמטרים ולכן ארחיב עליו אחרי הדוגמה הבאה.
לדוגמה, אם נרצה להקפיץ הודעה כאשר לוחצים על אלמנט מסוים, נצמיד לו מטפל לאירוע click בצורה הבאה:
var element = document.getElementById("ID"); // getElementById is just for example of course...
element.addEventListener("click", function() {
alert("Hello!");
});
element.addEventListener("click", function() {
alert("Hello!");
});
פשוט מאוד, לא?
הפרמטר useCapture
הפרמטר הזה בא לטפל במקרים מסוימים מאוד.
ניקח את הקוד הזה:
<div id="a">
a
<div id="b">
b
</div>
</div>
<script>
document.getElementById("a").addEventListener("click", function () {
alert("Need to be first!");
});
document.getElementById("b").addEventListener("click", function () {
alert("Hi!");
});
</script>
a
<div id="b">
b
</div>
</div>
<script>
document.getElementById("a").addEventListener("click", function () {
alert("Need to be first!");
});
document.getElementById("b").addEventListener("click", function () {
alert("Hi!");
});
</script>
המטרה שלנו כאן היא שכש-a נלחץ (גם אם אלמנט בתוכו נלחץ), "Need to be first" תמיד יוצג ראשון לפני כל מטפל אירוע אחר.
אם נריץ את הקוד ונלחץ על b נגלה שזה לא מה שקורה. קודם מוקפץ Hi ורק אז Need to be first. וזה לא מה שאנחנו רוצים.
למה זה קורה? כי ברגע שאנחנו לוחצים על b מופעלים קודם מטפלי האירועים של b, ורק אז מופעלים מטפלי האירועים של a (כי האמת שגם לחצו על a, אמנם לא ישירות). ככה ההירארכיה עובדת.
הפרמטר useCapture בא לפתור בדיוק את זה.
אם נעביר לו false, נקבל את ההתנהגות שראינו מקודם. לעומת זאת, אם נעביר true אז מטפל האירוע שרשמנו זה עתה יפעל תמיד לפני כל מטפל אירוע של אחד מהאלמנטים שמתחתיו בעץ ה-DOM (כלומר אלמנטים שהם בתוכו).
חשוב מאוד להבין: זה לא אומר שמטפל האירוע תמיד יופעל ראשון. אלא פשוט לפני כל מטפלי האירועים של האלמנטים שבתוכו (מתחתיו בעץ ההירארכיה). ככה שאם יש אלמנט מעליו (אלמנט שהוא "אבא" של האלמנט שלנו) אז מטפל האירועים של האבא יופעל ראשון (כמובן אם גם הוא נרשם עם useCapture=true).
ככה שאם נתקן את הקוד שלנו:
<div id="a">
a
<div id="b">
b
</div>
</div>
<script>
document.getElementById("a").addEventListener("click", function () {
alert("Need to be first!");
}, true);
document.getElementById("b").addEventListener("click", function () {
alert("Hi!");
});
</script>
a
<div id="b">
b
</div>
</div>
<script>
document.getElementById("a").addEventListener("click", function () {
alert("Need to be first!");
}, true);
document.getElementById("b").addEventListener("click", function () {
alert("Hi!");
});
</script>
עכשיו Need to be first יהיה ראשון כשנלחץ על b.
כמה מטפלים לאותו אירוע, אפשרי?
כמובן.
נראה את הקוד הבא:
<div id="a">
a
</div>
<script>
document.getElementById("a").addEventListener("click", function () {
alert("first event listener");
});
document.getElementById("a").addEventListener("click", function () {
alert("second event listener");
});
</script>
a
</div>
<script>
document.getElementById("a").addEventListener("click", function () {
alert("first event listener");
});
document.getElementById("a").addEventListener("click", function () {
alert("second event listener");
});
</script>
רשמנו את שני מטפלים לאותו אירוע. מה יקרה כשנלחץ על a?
נריץ ונראה שנקבל fisrt event listener ואז second event listener. שני מטפלי האירועים הופעלו.
אפשר לראות שמי שנרשם לאירוע ראשון מופעל ראשון. כל הקודם זוכה.
גם כאן useCapture משחק תפקיד, ואם ניתן למישהו useCapture=true אז הוא יופעל לפני כל השאר (גם אם הוא נרשם אחרון לאירוע).
הערה: אני יודע שיש גם את הפונקצייה attachEvent ועוד דרכים לרשום מטפלים לאירועים (כמו element.onclick או כמו inline javascript מה שנקרא), אבל מטרת הפוסט היא להסביר על אירועים ולא על תאימות לאחור עם דפדפנים ישנים.
מחיקת מטפלי אירועים
לפעמים אנחנו לא נרצה עוד להקפיץ הודעה למשתמש כשהוא לוחץ על הכפתור. מה נעשה אז?
במקרה כזה נצטרך למחוק את המטפל של האירוע click של הכפתור שמקפיץ את ההודעה המעצבנת.
את זה נעשה באמצעות הפונקציה removeEventListener.
הפונקציה removeEventListener מקבלת גם היא 3 פרמטרים:
type - סוג האירוע שלו אנחנו מוחקים את המטפל.
listener - המטפל של האירוע אותו אנחנו רוצים למחוק.
useCapture (לא חובה) - נעביר true אם המטפל שאותו אנחנו מוחקים נרשם כ-useCapture=true או false אם לא.
דוגמה שתמחיש את העניין:
<button id="a">a</button>
<button id="b">b</button>
<script>
function listener() {
alert("clicked!");
}
document.getElementById("a").addEventListener("click", listener);
document.getElementById("b").addEventListener("click", function () {
document.getElementById("a").removeEventListener("click", listener);
});
</script>
<button id="b">b</button>
<script>
function listener() {
alert("clicked!");
}
document.getElementById("a").addEventListener("click", listener);
document.getElementById("b").addEventListener("click", function () {
document.getElementById("a").removeEventListener("click", listener);
});
</script>
אפשר לראות כאן שכתבנו פונקצית listener פשוטה שמקפיצה הודעה ורשמנו את הפונקציה כמטפל לאירוע click של הכפתור a.
רשמנו גם מטפל לאירוע click של הכפתור b, שמוחק בעצם את המטפל listener של האירוע click של הכפתור a.
נריץ ונראה שאם אנחנו לוחצים על a אנחנו מקבלים הודעה. אבל אם נלחץ על b ואז נלחץ שוב על a, נפסיק לקבל הודעות. כי הלחיצה על b מחקה את המטפל של האירוע של a.
הפרמטר event
מטפל האירוע שלנו (ה-listener) מקבל גם פרמטר. הפרמטר הוא אובייקט מסוג Event (נדבר על הקונסטרקטור Event ועל אחרים בחלק ב').
לאובייקט הזה יש הרבה מאפיינים ופונקציות והם משתנים בסוגים שונים של אירועים, אציג כאן רק חלק (הרשימה המלאה):
currentTarget - האלמנט אליו רשמנו את האירוע (מומלץ להשתמש בזה ולא ב-this כי את this אפשר לשנות).
target - האלמנט המקורי ממנו הופעל האירוע. זה לא זהה לאלמנט אליו רשמנו את מטפל האירוע. במקרה שלחצנו על אחד מהאלמנטים בתוך האלמנט אליו רשמנו את מטפל האירוע אז currentTarget יחזיר לנו את אלמנט האבא אליו רשמנו את מטפל האירוע, אבל target יחזיר את האלמנט שעליו לחצנו, שהוא לא בהכרח האלמנט אליו רשמנו את מטפל האירוע.
type - מחזיר את סוג האירוע. click, mouseover וכו'.
preventDefault - מבטל את ההתנהגות הנורמלית של האירוע. לדוגמה:
<a href="http://google.co.il" id="google">Google</a>
<script>
document.getElementById("google").addEventListener("click", function (e) {
e.preventDefault();
});
</script>
<script>
document.getElementById("google").addEventListener("click", function (e) {
e.preventDefault();
});
</script>
כשנלחץ על הקישור לא נעבור לגוגל כיוון שהפעלנו את preventDefault שמונעת את התנהגות ברירת המחדל של האירוע על אותו אלמנט (במקרה שלנו - לחיצה על לינק שתוביל לעמוד אחר).
stopImmediatePropagation - אם נפעיל את הפונקציה הזו באחד מהמטפלים של אירוע מסוים, אז גם אם יש מטפלים נוספים לאותו אירוע, הם לא יופעלו באירוע הנוכחי.
דוגמה:
<button id="a">click</button>
<script>
document.getElementById("a").addEventListener("click", function (e) {
alert("Hello");
e.stopImmediatePropagation();
});
document.getElementById("a").addEventListener("click", function () {
alert("Hi!");
});
</script>
<script>
document.getElementById("a").addEventListener("click", function (e) {
alert("Hello");
e.stopImmediatePropagation();
});
document.getElementById("a").addEventListener("click", function () {
alert("Hi!");
});
</script>
אם נריץ ונלחץ על הכפתור נגלה שלא נקבל את ההודעה Hi אלא רק את Hello. למה? כי אחרי שהקפצנו את ההודעה Hello הפעלנו את stopImmediatePropagation ומנענו בעצם משאר מטפלי האירועים גם הם לפעול.
שימו לב: מטפל האירוע שמקפיץ את ההודעה Hi עדיין רשום לאירוע. לא מחקנו אותו. ככה שאם לדוגמה נמחוק את מטפל האירוע הראשון אז אנחנו נקבל את ההודעה Hi. חשוב להבין את זה.
stopPropagation - מונע מהאירוע להמשיך "להתפשט" ולהפעיל מטפלי אירועים באלמנטים אחרים במעלה עץ ה-DOM (אלמנטים שהם "הורים" של האלמנט שהפעיל את האירוע). דוגמה:
<div id="a">
a
<div id="b">
b
</div>
</div>
<script>
document.getElementById("a").addEventListener("click", function () {
alert("Hello");
});
document.getElementById("b").addEventListener("click", function (e) {
alert("Hi!");
e.stopPropagation();
});
</script>
a
<div id="b">
b
</div>
</div>
<script>
document.getElementById("a").addEventListener("click", function () {
alert("Hello");
});
document.getElementById("b").addEventListener("click", function (e) {
alert("Hi!");
e.stopPropagation();
});
</script>
אם נריץ ונלחץ על b נראה שאנחנו מקבלים את ההודעה Hi אבל לא מקבלים את ההודעה Hello. למה? כי ברגע שלחצנו על b והקפצנו את ההודעה Hi הפעלנו את stopPropagation שמנע מאלמנטים במעלה עץ ה-DOM (אלמנט כמו a לדוגמה) להפעיל גם הם את המטפלים שלהם לאותו אירוע. שימו לב שבלי stopPropagation היינו מקבלים כאן את ההודעה Hi ואז את Hello.
עד כאן חלק א' של המדריך.
בחלק ב' נדבר על דברים יותר מתקדמים כמו על הפעלה של אירועים מתוך הקוד ולאו דווקא על ידי המשתמש, נדבר על הקנסטרקטור Event ועל האחרים ש"יורשים" ממנו ונבין מה ההבדל בין כל אחד ואחד.
אני אולי גם אכתוב על יצירת אירועים משל עצמנו, אבל זה כבר לפרק ג' שכנראה יהיה הכי קצר מבין שאר הפרקים.
בקשה ממני: השתדלתי לתת קישורים למקורות שונים, השתמשו בהם כדי להבין את הנושא יותר טוב ולהכיר דברים מעבר למה שהצלחתי להכניס לפוסט. :)
תגובות לכתבה:
תודה רבה עידן
מאמר מעולה. עזר לי מאוד. תודה רבה. :-)
זה המדריך הכי טוב, הכי פשוט והכי מובן לאיבנטים שהי פעם ראיתי
מעולה, מעולה.
המון תודה.
המדריך מעולה :) מחכה לחלק הבא :)
תודה רבה לכולם. החלק השני יהיה יותר מתקדם ואסביר על הקונסטרקטורים של האירועים השונים, אתחול שלהם והפעלה שלהם מתוך הקוד. :)